package org.haredot.filter;

import io.jsonwebtoken.Claims;
import org.haredot.config.JwtToken;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 负责 验证 请求 头上 是否 包含 Authorization 头
 */
public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {

    private JwtToken jwtToken ;

    private UserDetailsService userDetailsService ;

    public BearerTokenAuthenticationFilter(JwtToken jwtToken, UserDetailsService userDetailsService) {
        this.jwtToken = jwtToken ;
        this.userDetailsService = userDetailsService;
        Assert.notNull(jwtToken, "jwtToken is missing");
    }

    /**
     * 返回一个错误的JSON数据
     * @param obj
     * @param status
     * @param response
     * @throws IOException
     */
    private void errorToJson(Object obj, HttpStatus status, HttpServletResponse response) throws IOException {
        ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
        servletServerHttpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
        // 响应一个失败的信息、且失败的原因是 令牌 过期
        new MappingJackson2HttpMessageConverter().write(obj,
                MediaType.APPLICATION_JSON, servletServerHttpResponse);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取 请求 Authorization 头
        String authorization = request.getHeader("Authorization");

        // 如果 认证头 不存在，则 继续向下执行 其他过滤器
        if (!StringUtils.hasText(authorization)) {
            filterChain.doFilter(request ,response);
            return ;
        }

        // 如果 有认证头 信息 ， 判断 该值 是否 以 Bearer 开头
        if (!authorization.startsWith("Bearer ")) {
            filterChain.doFilter(request ,response);
            return ;
        }
        // 获取 访问令牌
        String accessToken = authorization.substring(7) ;
        // 验证 令牌是否 过期
        boolean valid = jwtToken.isValidToken(accessToken) ;

        if (!valid) {
            Map<String, String> status = Map.of("status", "20000", "message", "令牌已过期或不正确");
            errorToJson(status, HttpStatus.BAD_REQUEST, response) ;
            return ;
        }

        // 获取 令牌的 Claims
        Claims claims = jwtToken.parseJwtToClaims(accessToken);

        //获取 Claims 中的 用户名
        String username = claims.getSubject() ;

        Date expiration = claims.getExpiration();
        // 判断是否过期
        if (expiration!=null && expiration.before(new Date())) {
            // 响应一个失败的信息、且失败的原因是 令牌 过期
            errorToJson(Map.of("status", "20000", "message", "令牌已过期"), HttpStatus.BAD_REQUEST, response) ;
            return ;
        }

        // 验证 令牌的 唯一标识
        valid = jwtToken.isValidJti(username, expiration, claims.getId()) ;
        if (!valid) {
            // 响应一个失败的信息、且失败的原因是 令牌 过期
            errorToJson(Map.of("status", "20000", "message", "令牌无效"), HttpStatus.BAD_REQUEST, response) ;
            return ;
        }

        Date notBefore = claims.getNotBefore();
        // 判断是否生效
        if (notBefore!=null && notBefore.after(new Date())) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            // 响应一个失败的信息、且失败的原因是 令牌 过期
            errorToJson(Map.of("status", "20000", "message", "令牌未生效"), HttpStatus.BAD_REQUEST, response) ;
            return ;
        }

        // 根据用户名，查询 该用户所拥有的权限列表
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        // 创建一个认证对象、并存储到 SecurityContext 上下文中
        UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 继续向下执行
        filterChain.doFilter(request, response);

    }
}
